// Copyright 2005 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package org.apache.tapestry5.internal.portlet.services;

import java.util.List;
import java.util.Map;

import javax.portlet.BaseURL;
import javax.portlet.PortletSecurityException;

import org.apache.tapestry5.Link;
import org.apache.tapestry5.internal.services.LinkSecurity;
import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
import org.apache.tapestry5.ioc.internal.util.InternalUtils;
import org.apache.tapestry5.portlet.PortletConstants;
import org.apache.tapestry5.services.ContextPathEncoder;
import org.apache.tapestry5.services.Response;

/**
 * Wrapper around {@link javax.portlet.PortletURL}.
 * 
 * @author Howard M. Lewis Ship
 * @since 4.0
 */
public class LinkImplPortlet implements Link {

    private Map<String, String>      parameters;

    protected final BaseURL          portletURL;

    protected final String           pagenameWithContext;

    private final boolean            forForm;

    private LinkSecurity             defaultSecurity;

    private final Response           response;

    private String                   anchor;

    private final ContextPathEncoder contextPathEncoder;

    public LinkImplPortlet(BaseURL portletURL, String pagenameWithContext, boolean forForm,
                           LinkSecurity defaultSecurity, Response response, ContextPathEncoder contextPathEncoder) {
        this.portletURL = portletURL;
        this.pagenameWithContext = pagenameWithContext;
        this.forForm = forForm;
        this.defaultSecurity = defaultSecurity;
        this.response = response;
        this.contextPathEncoder = contextPathEncoder;
    }

    @Override
    public void addParameter(String parameterName, String value) {
        assert InternalUtils.isNonBlank(parameterName);
        assert InternalUtils.isNonBlank(value);

        if (this.parameters == null) {
            this.parameters = CollectionFactory.newMap();
        }

        this.parameters.put(parameterName, value);
    }

    @Override
    public String getAnchor() {
        return this.anchor;
    }

    @Override
    public List<String> getParameterNames() {
        return InternalUtils.sortedKeys(this.parameters);
    }

    @Override
    public String getParameterValue(String name) {
        return InternalUtils.get(this.parameters, name);
    }

    @Override
    public void setAnchor(String anchor) {
        this.anchor = anchor;
    }

    @Override
    public String toAbsoluteURI() {
        return this.buildAnchoredURI(this.defaultSecurity.promote());
    }

    @Override
    public String toAbsoluteURI(boolean secure) {
        return this.buildAnchoredURI(secure ? LinkSecurity.FORCE_SECURE : LinkSecurity.FORCE_INSECURE);
    }

    @Override
    public String toRedirectURI() {
        return this.appendAnchor(this.response.encodeRedirectURL(this.buildURI(this.defaultSecurity)));
    }

    @Override
    public String toURI() {
        return this.buildAnchoredURI(this.defaultSecurity);
    }

    private String appendAnchor(String path) {
        return InternalUtils.isBlank(this.anchor) ? path : path + "#" + this.anchor;
    }

    private String buildAnchoredURI(LinkSecurity security) {
        return this.appendAnchor(this.response.encodeURL(this.buildURI(security)));
    }

    /**
     * Returns the value from {@link #toURI()}
     */
    @Override
    public String toString() {
        return this.toURI();
    }

    /**
     * Extends the absolute path with any query parameters. Query parameters are never added to a forForm link.
     *
     * @return absoluteURI appended with query parameters
     */
    protected String buildURI(LinkSecurity security) {
        this.portletURL.setParameter(PortletConstants.PORTLET_PAGE, this.pagenameWithContext);

        try {
            switch (security) {
                case FORCE_SECURE:
                    this.portletURL.setSecure(true);
                    break;
                case FORCE_INSECURE:
                    this.portletURL.setSecure(false);
                    break;
                default:
            }
        }
        catch (PortletSecurityException e) {
            // rethrow
            throw new RuntimeException(e);
        }

        StringBuilder builder = new StringBuilder(300);

        builder.append(this.unencode(this.portletURL.toString()));

        // FS: if forForm ???
        String sep = "?";

        for (String name : this.getParameterNames()) {
            String value = this.parameters.get(name);
            builder.append(sep);
            builder.append(name);
            builder.append("=");
            builder.append(value);
            sep = "&";
        }

        return builder.toString();
    }

    /**
     * The PortletURL class returns a url that's already XML-escaped, ready for
     * inclusion directly into the response stream. However, the IMarkupWriter
     * expects to do that encoding too ... and double encoding is bad. So we
     * back out the most likely encoding (convert '&amp;amp;' to just '&amp;').
     */
    protected String unencode(String url) {
        StringBuffer buffer = new StringBuffer(url.length());
        String text = url;

        while (true) {
            int ampx = text.indexOf("&amp;");

            if (ampx < 0) {
                break;
            }

            // Take up to and including the '&'

            buffer.append(text.substring(0, ampx + 1));

            text = text.substring(ampx + 5);
        }

        buffer.append(text);

        return buffer.toString();
    }

    @Override
    public Link addParameterValue(String parameterName, Object value) {
        this.addParameter(parameterName, this.contextPathEncoder.encodeValue(value));

        return this;
    }

    @Override
    public void removeParameter(String parameterName) {

        assert InternalUtils.isNonBlank(parameterName);
        if (this.parameters != null) {
            this.parameters.remove(parameterName);
        }
    }

    @Override
    public String getBasePath() {
        throw new UnsupportedOperationException();
    }

    @Override
    public Link copyWithBasePath(String basePath) {
        throw new UnsupportedOperationException();
    }

}
